"""
Base classes for event handlers in the continual learning event system.

This module provides the foundational classes for implementing event handlers
that respond to training events. The base classes establish common patterns
for handler implementation, error handling, and conditional execution.

Handler Types:
- BaseEventHandler: Abstract base class implementing EventHandler interface
- ConditionalEventHandler: Executes only for specific event types
- TaskSpecificHandler: Executes only for specific task IDs

Design Patterns:
- Template Method: BaseEventHandler defines common structure
- Strategy Pattern: Different handlers implement different response strategies
- Interface Segregation: Clean separation between interface and implementation

Example:
    >>> from learners.events.handlers.base import BaseEventHandler
    >>> from learners.interfaces.event import EventContext
    >>>
    >>> class MyHandler(BaseEventHandler):
    ...     def handle(self, context: EventContext) -> None:
    ...         print(f"Handling {context.event.value}")
    >>>
    >>> handler = MyHandler("my_handler")
    >>> handler.handle(context)

Architecture:
    All event handlers inherit from BaseEventHandler which provides:
    - Consistent error handling and logging
    - Enable/disable functionality for dynamic control
    - Name-based identification for management
    - Standard interface compliance with EventHandler
"""

import logging
from abc import abstractmethod

from learners.interfaces.event import EventHandler, EventContext, EventType
from learners.events.config import HandlerConfig


class BaseEventHandler(EventHandler):
    """
    Abstract base class for event handlers implementing the EventHandler interface.

    This class provides the foundation for all event handlers in the system,
    establishing common patterns for error handling, enable/disable functionality,
    and handler identification. All concrete handlers should inherit from this class.

    Features:
    - Automatic error handling with logging
    - Enable/disable toggle for dynamic control
    - Name-based identification for management
    - Full EventHandler interface compliance

    Attributes:
        name: Human-readable identifier for the handler
        _enabled: Whether the handler is currently active
        _supported_events: Set of event types this handler supports

    Example:
        >>> class LoggingHandler(BaseEventHandler):
        ...     def handle(self, context: EventContext) -> None:
        ...         logging.info(f"Event: {context.event.value}")
        >>>
        >>> handler = LoggingHandler("event_logger")
        >>> handler.disable()  # Temporarily disable
        >>> handler.enable()   # Re-enable
    """

    def __init__(self, config: HandlerConfig, *args, **kwargs):
        """
        Initialize the base event handler.
        """
        self.name = kwargs.get("name", None) or self.__class__.__name__
        self._enabled = config.enabled
        self._events = config.events
        self.config = config.config

    @property
    def enabled(self) -> bool:
        """Check if handler is enabled."""
        return self._enabled

    def enable(self) -> None:
        """Enable the handler for event processing."""
        self._enabled = True

    def disable(self) -> None:
        """Disable the handler to prevent event processing."""
        self._enabled = False

    def can_handle(self, event: EventType) -> bool:
        """
        Check if this handler can handle the given event type.

        Args:
            event: Type of event to check

        Returns:
            True if handler can handle this event type
        """
        if not self._enabled:
            return False

        if self._events is None:
            return True

        return event in self._events

    def handle(self, context: EventContext) -> None:
        """
        Handle the event with enable/disable check and error handling.

        This method implements the common handler pattern:
        1. Check if handler can handle this event type
        2. Call the abstract _handle_event method
        3. Handle any exceptions with logging

        Args:
            context: Event context containing event data and learner state
        """
        if not self.can_handle(context.event):
            return

        try:
            self._handle_event(context)
        except Exception as e:
            logging.error(f"Error in handler {self.name}: {e}")

    @abstractmethod
    def _handle_event(self, context: EventContext) -> None:
        """
        Handle the event - to be implemented by subclasses.

        This is the core method that subclasses must implement to define
        their specific event handling logic. It receives the event context
        and performs the handler-specific operations.

        Args:
            context: Event context with event data and learner state

        Note:
            Implementations should not need to handle exceptions or
            check enabled state - the base class handles this.
        """
        pass


class TaskSpecificHandler(BaseEventHandler):
    """
    Event handler that only executes for specific tasks.

    This handler filters events based on task ID, allowing handlers
    to be configured for specific tasks in continual learning scenarios.
    Useful for task-specific logging, metrics, or interventions.

    Attributes:
        target_tasks: Set of task IDs this handler should process

    Example:
        >>> # Handler that only runs for tasks 0, 2, and 4
        >>> handler = TaskSpecificHandler([0, 2, 4], name="even_task_handler")
        >>>
        >>> # Will only execute for specified tasks
        >>> handler.handle(task_1_context)  # Does nothing
        >>> handler.handle(task_2_context)  # Executes _handle_event method
    """

    def __init__(self, config: HandlerConfig, target_tasks: list[int], *args, **kwargs):
        super().__init__(config, *args, **kwargs)
        """
        Initialize task-specific handler with target task IDs.
        """
        self.target_tasks = set(target_tasks)

    def can_handle(self, event: EventType) -> bool:
        """
        Check if this handler can handle the given event type and current task.

        Args:
            event: Type of event to check

        Returns:
            True if handler can handle this event type
        """
        return super().can_handle(event)

    def handle(self, context: EventContext) -> None:
        """
        Handle the event only if it matches target tasks.

        Args:
            context: Event context to potentially handle
        """
        if context.task_id is not None and context.task_id not in self.target_tasks:
            return

        super().handle(context)
